package ru.vsu.amm.fuzzy.problems.ui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;

import javax.swing.JCheckBoxMenuItem;

import ru.vsu.amm.algebra.Matrix;
import ru.vsu.amm.fuzzy.Convolutions;
import ru.vsu.amm.fuzzy.FuzzyUtils;
import ru.vsu.amm.fuzzy.Quantifier;
import ru.vsu.amm.fuzzy.Scale;
import ru.vsu.amm.io.FuzzyIO;
import ru.vsu.amm.io.MatrixIO;
import ru.vsu.amm.utils.CommonUtils;

/**
 * 
 * @author Иванов Илья
 * @since 21 января 2013
 *
 */
public enum OperatorController {
		
	CONVOLUTION("Свёртка") {
		public void updateContent(FuzzyFrame frame) {
			frame.showConvolution();
		}
		
		public void calculate(FuzzyFrame frame) {
			List<Double[]> scoresVectors = readScoresVectors(Double.class, frame);
			if (scoresVectors == null)
				return;
			
			Double[] weightsVector = null;
			double[] unboxedWeightsVector = null;
			if (frame.isPairedComparisons()) {
				Matrix matrix;
				try {
					matrix = MatrixIO.readFromFile(frame.getAdditionalFilePath());
				} catch (Exception e) {
					frame.showErrorMessage(INCORRECT_PAIRED_COMPARISONS_FILE);
					return;
				}
				if (!FuzzyUtils.checkPairedComparisonsMatrix(matrix)) {
					frame.showErrorMessage(INCORRECT_PAIRED_COMPARISONS_MATRIX);
					return;
				}
				try {
					unboxedWeightsVector = matrix.maxEigenvector(EIGENALG_PRECISION).getColumn(0);
					weightsVector = CommonUtils.boxArray(unboxedWeightsVector);
				} catch (Exception e) {
					frame.showErrorMessage(EIGENVECTOR_FAILED);
					return;
				}
			} else if (frame.isCustomWeights())
				weightsVector = readWeightsVector(Double.class, frame);
			
			if (weightsVector == null)
				return;
			
			if (unboxedWeightsVector == null)
				unboxedWeightsVector = CommonUtils.unboxArray(weightsVector);
			if (!FuzzyUtils.checkWeightsVector(unboxedWeightsVector)) {
				frame.showErrorMessage(INCORRECT_WEIGHTS_VECTOR);
				return;
			}
			if (weightsVector.length != scoresVectors.get(0).length) {
				frame.showErrorMessage(INCOMPATIBLE_DIMENSIONS);
				return;
			}
			
			Double[][] additives = new Double[scoresVectors.size()][1];
			for (int i = 0; i < scoresVectors.size(); i++)
				additives[i][0] = Convolutions.additive(unboxedWeightsVector, CommonUtils.unboxArray(scoresVectors.get(i)));
			
			writeResults(weightsVector, additives, Double.class, Double.class, frame);
		}
	},
	OWA("OWA") {
		public void updateContent(FuzzyFrame frame) {
			frame.showOwaLowa(getText());
		}

		public void calculate(FuzzyFrame frame) {
			calcOwaLowa(Double.class, frame);
		}
	},
	LOWA("LOWA") {
		public void updateContent(FuzzyFrame frame) {
			frame.showOwaLowa(getText());
		}
		
		public void calculate(FuzzyFrame frame) {
			calcOwaLowa(Scale.class, frame);
		}
	},
	LMIN_LMAX("LMin / LMax") {
		public void updateContent(FuzzyFrame frame) {
			frame.showLMinLMax();
		}
		
		public void calculate(FuzzyFrame frame) {
			List<Scale[]> scoresVectors = readScoresVectors(Scale.class, frame);
			if (scoresVectors == null)
				return;
			
			Scale[] weights = readWeightsVector(Scale.class, frame);
			if (weights == null)
				return;
			
			Scale[][] results = new Scale[scoresVectors.size()][2];
			for (int i = 0; i < results.length; i++) {
				results[i][0] = ru.vsu.amm.fuzzy.LMIN_LMAX.calcLMIN(weights, scoresVectors.get(i));
				results[i][1] = ru.vsu.amm.fuzzy.LMIN_LMAX.calcLMAX(weights, scoresVectors.get(i));
			}
			
			writeResults(weights, results, Scale.class, Scale.class, frame);
		}
	};
	
	private static final String INCORRECT_SCORES_FILE = "Невозможно прочитать файл с векторами оценок";
	private static final String EMPTY_SCORES_FILE = "Файл с векторами оценок пуст";
	private static final String DIFFERENT_SCORES_VECTORS = "Вектора оценок различной размерности";
	private static final String INCORRECT_WEIGHTS_FILE = "Невозможно прочитать файл с вектором весов";
	private static final String EMPTY_WEIGHTS_FILE = "Файл с вектором весов пуст";
	private static final String INCORRECT_PAIRED_COMPARISONS_FILE = "Невозможно прочитать файл с матрицей парных сравнений";
	private static final String INCORRECT_PAIRED_COMPARISONS_MATRIX = "Матрица парных сравнений задана некорректно";
	private static final String EIGENVECTOR_FAILED = "Не удалось найти вектор весов: не достигнута точность при\nпоиске собственного вектора матрицы парных сравнений";
	private static final String INCORRECT_WEIGHTS_VECTOR = "Вектор весов задан некорректно";
	private static final String INCOMPATIBLE_DIMENSIONS = "Размерности вектора весов и векторов оценок не совпадают";
	private static final String DATA_IS_NOT_SAVED_PATTERN = "Данные не сохранены:\n%s";
	private static final String FILE_IS_NOT_CHOSEN = "не выбран файл для записи";
	private static final String IO_EXCEPTION_OCCURED = "возникла ошибка ввода-вывода";
	private static final String DATA_SAVING_SUCCEEDED = "Данные успешно сохранены";
	private static final String INCORRECT_EXPONENT = "Показатель степени функции квантификации задан некорректно";
	
	private static final double EIGENALG_PRECISION = 1E-10;
	
	private String text;
	
	private OperatorController(String text) {
		this.text = text;
	}
	
	public String getText() {
		return text;
	}
	
	public OperatorMenuItem getMenuItem(final FuzzyFrame frame) {
		OperatorMenuItem item = new OperatorMenuItem(text, this);
		item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				frame.setCurrentController(OperatorController.this, false);
			}
		});
		
		return item;
	}
	
	public abstract void updateContent(FuzzyFrame frame);
	public abstract void calculate(FuzzyFrame frame);
	
	private static <T> List<T[]> readScoresVectors(Class<T> scoreClass, FuzzyFrame frame) {
		List<T[]> scoresVectors = FuzzyIO.readScores(frame.getScoresFilePath(), scoreClass);
		if (scoresVectors == null) {
			frame.showErrorMessage(INCORRECT_SCORES_FILE);
			return null;
		}
		if (scoresVectors.isEmpty()) {
			frame.showInformMessage(EMPTY_SCORES_FILE);
			return null;
		}
		if (!FuzzyUtils.checkScoresVectors(scoresVectors)) {
			frame.showErrorMessage(DIFFERENT_SCORES_VECTORS);
			return null;
		}
		
		return scoresVectors;
	}
	
	private static <T> T[] readWeightsVector(Class<T> weightClass, FuzzyFrame frame) {
		List<T[]> weightsVectors = FuzzyIO.readScores(frame.getAdditionalFilePath(), weightClass);
		if (weightsVectors == null) {
			frame.showErrorMessage(INCORRECT_WEIGHTS_FILE);
			return null;
		}
		if (weightsVectors.isEmpty()) {
			frame.showErrorMessage(EMPTY_WEIGHTS_FILE);
			return null;
		}
		
		return weightsVectors.get(0);
	}
	
	private static <T, V> void writeResults(T[] weights, V[][] results, Class<T> weightClass, Class<V> resultClass, FuzzyFrame frame) {
		String fileForSaving = frame.chooseFileForSaving();
		if (fileForSaving == null) {
			frame.showExclamationMessage(String.format(DATA_IS_NOT_SAVED_PATTERN, FILE_IS_NOT_CHOSEN));
			return;
		}
		
		try {
			FuzzyIO.writeAggregationResults(frame.isOutputWeights() ? weights : null, results, weightClass, resultClass, fileForSaving);
			frame.showInformMessage(DATA_SAVING_SUCCEEDED);
		} catch (Exception e) {
			frame.showErrorMessage(String.format(DATA_IS_NOT_SAVED_PATTERN, IO_EXCEPTION_OCCURED));
		}
	}
	
	@SuppressWarnings("unchecked")
	private static <T> void calcOwaLowa(Class<T> scoreClass, FuzzyFrame frame) {
		List<T[]> scoresVectors = readScoresVectors(scoreClass, frame);
		if (scoresVectors == null)
			return;
		
		double exponent = -1.0;
		try {
			exponent = Double.valueOf(frame.getExponent());
		} catch (NumberFormatException e) {}
		if (exponent <= 0) {
			frame.showErrorMessage(INCORRECT_EXPONENT);
			return;
		}
		
		T[][] results = null;
		double[] weights = new Quantifier.Power(exponent).getWeightVector(scoresVectors.get(0).length);
		if (scoreClass == Double.class) {
			results = (T[][]) new Double[scoresVectors.size()][1];
			for (int i = 0; i < scoresVectors.size(); i++)
				results[i][0] = (T) Double.valueOf(Convolutions.OWA(weights, CommonUtils.unboxArray((Double[]) scoresVectors.get(i))));
		} else if (scoreClass == Scale.class) {
			results = (T[][]) new Scale[scoresVectors.size()][1];
			for (int i = 0; i < scoresVectors.size(); i++)
				results[i][0] = (T) ru.vsu.amm.fuzzy.LOWA.calc((List<Scale>) Arrays.asList(scoresVectors.get(i)), weights);
		}
		if (results == null)
			return;
		
		writeResults(CommonUtils.boxArray(weights), results, Double.class, scoreClass, frame);
	}
	
	@SuppressWarnings("serial")
	public static class OperatorMenuItem extends JCheckBoxMenuItem {
		
		private OperatorController controller;
		
		public OperatorMenuItem(String text, OperatorController controller) {
			super(text);
			this.controller = controller;
		}
		
		public OperatorController getController() {
			return controller;
		}
	}
}